home *** CD-ROM | disk | FTP | other *** search
- #include <stdio.h>
- #include <string.h>
- #include <stdlib.h>
- #include <unistd.h>
- #include <ctype.h>
- #include <pwd.h>
- #include <errno.h>
- #include <time.h>
- #include <sys/stat.h>
- #include "internal.h"
- #include "userconf.h"
- #include "../paths.h"
- //#include "../xconf/xconf.h"
- #include "userconf.m"
-
- static USERCONF_HELP_FILE help_user ("user");
- USERCONF_HELP_FILE help_password ("password");
-
-
- PRIVATE void USER::init(
- const char *_name,
- const char *_passwd,
- int _uid,
- int _gid,
- const char *_gecos,
- const char *_dir,
- const char *_shell)
- {
- name.setfrom (_name);
- passwd.setfrom (_passwd);
- uid = _uid;
- gid = _gid;
- comment.setfrom (_gecos);
- wrkdir.setfrom (_dir);
- shell.setfrom (_shell);
- special = !shells_isok(_shell);
- }
-
- PUBLIC USER::USER(
- const char *_name,
- const char *_passwd,
- int _uid,
- int _gid,
- const char *_gecos,
- const char *_dir,
- const char *_shell)
- {
- init (_name,_passwd,_uid,_gid,_gecos,_dir,_shell);
- }
-
- PUBLIC USER::USER()
- {
- init ("","",-1,-1,"","","");
- }
- /*
- Decompose a /etc/passwd style line into words
- */
- int user_splitline (const char *line, char words[9][100])
- {
- int nbword = 0;
- char *dst = words[0];
- for (int i=0; i<9; i++) words[i][0] = '\0';
- while (*line != '\0' && *line != '\n'){
- if (*line == ':'){
- line++;
- *dst = '\0';
- nbword++;
- dst = words[nbword];
- }else{
- *dst++ = *line++;
- }
- }
- *dst = '\0';
- return nbword;
- }
- /*
- Taken from /etc/passwd directly
- */
- PUBLIC USER::USER(const char *line)
- {
- char words[9][100];
- user_splitline (line,words);
- name.setfrom (words[0]);
- passwd.setfrom (words[1]);
- uid = atoi(words[2]);
- gid = atoi(words[3]);
- comment.setfrom (words[4]);
- wrkdir.setfrom (words[5]);
- shell.setfrom (words[6]);
- special = !shells_isok(words[6]);
- }
- PUBLIC USER::USER(struct passwd *p)
- {
- init (p->pw_name,p->pw_passwd,p->pw_uid,p->pw_gid,p->pw_gecos,p->pw_dir
- ,p->pw_shell);
- }
- PUBLIC USER::~USER()
- {
- }
- /*
- Return the crypt password field
- */
- PUBLIC const char *USER::getpwd()
- {
- return passwd.get();
- }
-
- /*
- Write one record of /etc/passwd
- */
- PUBLIC void USER::write(FILE *fout)
- {
- fprintf (fout,"%s:%s:%d:%d:%s:%s:%s\n"
- ,name.get(),passwd.get(),uid,gid,comment.get()
- ,wrkdir.get(),shell.get());
- }
- /*
- Return the login name of the user.
- */
- PUBLIC const char *USER::getname()
- {
- return name.get();
- }
- /*
- Return the long informative name of the user.
- */
- PUBLIC const char *USER::getgecos()
- {
- return comment.get();
- }
- /*
- Return the User ID of the user.
- */
- PUBLIC int USER::getuid()
- {
- return uid;
- }
- /*
- Return the User GID of the user.
- */
- PUBLIC int USER::getgid()
- {
- return gid;
- }
- /*
- Return the shell (path) of the user.
- If the user has no explicit shell, return the default.
- */
- PUBLIC const char *USER::getshell()
- {
- return shell.is_empty() ? shells_getdefault() : shell.get();
- }
- /*
- Return != 0 if this user account is a special admin account
- unrelated to any user.
- */
- PUBLIC int USER::is_admin()
- {
- const char *pt = name.get();
- return strcmp(pt,"root")==0
- || strcmp(pt,"adm")==0
- || strcmp(pt,"bin")==0
- || strcmp(pt,"daemon")==0
- || strcmp(pt,"sys")==0
- || strcmp(pt,"postmaster")==0
- || strcmp(pt,"usenet")==0
- || strcmp(pt,"sync")==0;
- }
- /*
- Return != 0 if this user account is a special non user account
- generally intended for machine to machine connection like uucp.
- */
- PUBLIC int USER::is_special()
- {
- return special;
- }
- /*
- Check if a user is correctly configured.
- Return -1 if not.
- */
- PRIVATE int USER::check(
- USERS &users,
- GROUPS &groups,
- int full) // Check everything, even directory
- // access and so on
- {
- char status[1000];
- status[0] = '\0';
- USER *other = users.getitem(name.get());
- if (other != NULL && other != this){
- strcat (status,MSG_U(E_DUPLOGIN,"User already exist (login name)\n"));
- }
- other = users.getfromuid(uid);
- if (other != NULL && other != this){
- strcat (status,MSG_U(E_DUPID,"User already exist (User ID)\n"));
- }
- if (groups.getfromgid(gid)==NULL){
- strcat (status,MSG_U(E_UNKNOWNGRP,"Group do not exist\n"));
- }
- if (!special && !shells_isok(shell.get())){
- strcat (status,MSG_U(E_IVLDSHELL,"Invalid command interpretor\n"));
- }else if (!shells_exist(shell.get())){
- strcat (status,MSG_U(E_NOSHELL,"Command interpretor not available\n"));
- }
- char status_dir[200];
- int code_dir = checkhome(status_dir);
- /* #Specification: userconf / user / home directory
- userconf do not allow a user to have an empty directory
- nor a directory which point to something else than
- a directory.
- */
- if (code_dir != 0 && (full || code_dir != ENOENT)){
- strcat (status,status_dir);
- }
- int ret = 0;
- if (status[0] != '\0'){
- xconf_error ("%s",status);
- ret = -1;
- }
- return ret;
- }
-
- /*
- Check if the home of the user do exist
- Return -1 : No home directory specified
- ENOENT : path does not exist.
- ENOTDIR : path do exist, but is not a directory.
- EPERM : path do exist but permissions are wrong
- 0 : all is ok.
- */
- PUBLIC int USER::checkhome (
- char *status) // Will contain an explanation of the problem
- // if any (may be NULL)
- {
- int ret = -1;
- const char *msg = MSG_U(E_NOHOME,"No home directory specified");
- if (!wrkdir.is_empty()){
- struct stat st;
- if (stat(wrkdir.get(),&st)!=-1){
- if (S_ISDIR (st.st_mode)){
- /* #Specification: user / home directory / owner
- userconf check that the home directory
- of a user is own (gid and uid) by him
- except for special account (
- administrative, PPP, uucp, etc...)
- where we generally allocate the same
- directory to a bunch of users.
- */
- if (special
- || (st.st_uid == uid && st.st_gid == gid)){
- ret = 0;
- msg = MSG_U(N_IS_OK,"is ok");
- }else{
- ret = EPERM;
- if (st.st_uid == uid){
- msg = MSG_U(E_IVLDGRP,"have invalid group");
- }else if (st.st_gid == gid){
- msg = MSG_U(E_IVLDOWN,"have invalid owner");
- }else{
- msg = MSG_U(E_IVLDG_O,"have invalid owner and group");
- }
- }
- }else{
- ret = ENOTDIR;
- msg = MSG_U(E_EXISTDIR,"do exist, but is not a directory");
- }
- }else{
- msg = MSG_U(E_DONOTEXIST,"does not exist");
- ret = ENOENT;
- }
- }
- if (status != NULL){
- sprintf (status
- ,MSG_U(E_HOMEUSER,"Home directory of user %s: %s\n%s\n")
- ,name.get(),wrkdir.get(),msg);
- }
- return ret;
- }
- /*
- Set the name of the new user
- */
- PUBLIC void USER::setname(const char *_name)
- {
- if (_name != NULL) name.setfrom (_name);
- }
-
- /*
- Create/update the user home directory.
- Return -1 if any error.
- */
- PUBLIC int USER::sethome ()
- {
- char status[200];
- int ret = checkhome(status);
- if (ret != 0){
- if (ret == -1 || ret == ENOTDIR){
- xconf_error ("%s",status);
- }else if (perm_rootaccess(
- MSG_U(P_SETUSERDIR,"set user %s directory\n")
- ,name.get())){
- if (ret == ENOENT){
- ret = mkdir (wrkdir.get(),0755);
- if (ret == 0) ret = EPERM;
- }
- if (ret == EPERM){
- if (chown (wrkdir.get(),uid,gid) != -1){
- ret = 0;
- }
- }
- if (ret != 0){
- xconf_error (
- MSG_U(E_SETUPDIR
- ,"Can't setup user %s's home directory %s\n"
- "reason: %s\n")
- ,name.get(),wrkdir.get()
- ,strerror (errno));
- }
- }
- }
- return ret;
- }
-
- static int user_str2gid(
- GROUPS &groups,
- SSTRING &group)
- {
- /* #Specification: user record / gid / format
- GID may be entered either as a string (a group name)
- or as a number.
- */
- const char *str = group.get();
- int gid = groups.getgid(str);
- if (gid == -1 && isdigit(str[0])) gid = atoi(str);
- return gid;
- }
-
- /*
- Edit the specification of a user.
- Return -1 if the user escape without accepting the changes.
- Return 0 if the user accepted the change
- Return 1 if the user wish to delete this record.
- */
- PUBLIC int USER::edit(USERS &users, GROUPS &groups, int is_new)
- {
- DIALOG dia;
- dia.newf_str (MSG_U(F_LOGIN,"Login name"),name);
- dia.newf_str (MSG_U(F_FULLNAME,"Full name"),comment);
- SSTRING group;
- group.setfrom (gid);
- if (gid == -1){
- group.setfrom (groups.getdefault());
- shell.setfrom (shells_getdefault());
- }else{
- GROUP *grp = groups.getfromgid(gid);
- if (grp != NULL){
- group.setfrom (grp->getname());
- }
- }
- FIELD_COMBO *grpl = dia.newf_combo (MSG_U(F_GROUP,"group"),group);
- {
- groups.sortbyname();
- for (int i=0; i<groups.getnb(); i++){
- grpl->addopt (groups.getitem(i)->getname());
- }
- }
- FIELD *fhome = dia.newf_str (MSG_U(F_HOME,"Home directory(opt)")
- ,wrkdir);
- dia.newf_str (MSG_U(F_SHELL,"Command interpreter(opt)")
- ,shell);
- SSTRING struid;
- if (uid != -1) struid.setfrom (uid);
- FIELD *fuid = dia.newf_str (MSG_U(F_UID,"User ID(opt)"),struid);
- if (is_special()){
- fhome->set_readonly();
- grpl->set_readonly();
- fuid->set_readonly();
- }
- SHADOW *shadow = NULL;
- int add_shadow = 0;
- if (shadow_exist()){
- shadow = users.getshadow(this);
- if (shadow == NULL){
- shadow = new SHADOW;
- shadow->passwd.setfrom (passwd);
- passwd.setfrom ("x");
- add_shadow = 1;
- }
- dia.newf_title ("",MSG_U(T_PASSMNG,"Password management"));
- dia.newf_num (MSG_U(F_PASSMAY,"Must keep # days"),shadow->may);
- dia.newf_num (MSG_U(F_PASSMUST,"Must change after # days"),shadow->must);
- dia.newf_num (MSG_U(F_PASSWARN,"Warn # days before expieration"),shadow->warn);
- dia.newf_num (MSG_U(F_PASSEXPIRE,"Account expire after # days"),shadow->expire);
- }
- int field = 0;
- int ret = -1;
- while (1){
- MENU_STATUS code = dia.edit (
- MSG_U(T_USERINFO,"User information")
- ,MSG_U(I_USERINTRO
- ,"You must specify at least the name\n"
- "and the full name")
- ,help_user.getpath()
- ,field
- ,MENUBUT_ACCEPT|MENUBUT_CANCEL|MENUBUT_DEL);
- if (code == MENU_CANCEL || code == MENU_ESCAPE){
- break;
- }else if (!perm_rootaccess(
- MSG_U(P_USERDATA
- ,"to maintain the user database"))){
- dia.restore();
- }else if (code == MENU_DEL){
- if (xconf_areyousure(MSG_U(Q_DELUSER
- ,"Confirm deletion of user account"))){
- ret = 1;
- break;
- }
- }else{
- /* #Specification: userconf / user account / semicolon
- A check is made to ensure that the user
- has not entered a : in any field
- during edition.
- */
- if (name.strchr(':') != NULL
- || comment.strchr(':') != NULL
- || group.strchr(':') != NULL
- || struid.strchr(':') != NULL
- || shell.strchr (':') != NULL
- || wrkdir.strchr (':') != NULL){
- xconf_error (MSG_U(E_NO2PT
- ,"No : in any field allowed"));
- }else{
- gid = user_str2gid(groups,group);
- uid = struid.is_empty()
- ? users.getnewuid(gid)
- : struid.getval();
- if (wrkdir.is_empty()){
- /* #todo: userconf / default home directory
- We currently force every new user into /home/...
- unless specified. This should be configurable
- maybe.
- */
- char buf[50];
- sprintf (buf,"/home/%s",name.get());
- wrkdir.setfrom (buf);
- }
- if (check(users,groups,0)==0){
- /* #Specification: user edit / bad html dialog
- This is a good exemple of a bad dialog
- (or at least complex dialog) for html mode
- All side effect of the dialog must be done
- at the exit. The do_sethome kludge is there
- just for that.
- Anyway, this dialog is bad and should contain
- the passwords (2 lines) when creating a new
- account. Flat dialog are generally nicer.
- */
- int do_sethome = 0;
- char status[200];
- int code = checkhome (status);
- if (code == ENOTDIR){
- xconf_error ("%s",status);
- }else if (code != 0){
- if (code == ENOENT){
- strcat (status
- ,MSG_U(Q_CREATE,"\nDo you want to create it ?"));
- }else if (code == EPERM){
- strcat (status
- ,MSG_U(Q_FIXIT,"\nDo you want to fix it ?"));
- }
- if (xconf_yesno(
- MSG_U(Q_USERHOME,"User home directory")
- ,status,help_nil)==MENU_YES){
- do_sethome = 1;
- }
- }
- if (checkhome(NULL)==0 || do_sethome){
- ret = 0;
- if (is_new) ret = editpass(1,shadow);
- if (ret == 0){
- if (do_sethome) sethome();
- setmodified();
- if (shadow != NULL){
- shadow->name.setfrom (name);
- if (add_shadow) users.addshadow (shadow);
- }
- }
- break;
- }
- }
- }
- }
- }
- if (ret != 0){
- dia.restore();
- gid = user_str2gid(groups,group);
- uid = struid.getval();
- }
- return ret;
- }
- /*
- Check if a password is weak
- */
- static int pass_isweak(const char *pass)
- {
- /* #Specification: userconf / net password / rejected
- New password are validated with some rules to ensure
- they are difficult enough. Here are the rules.
-
- #
- -6 chars minimum
- -Must have at least one non-letter character
- #
- */
- int ret = 1;
- if (pass[0] == '\0' || strcmp(pass,"*")==0){
- /* #Specification: userconf / user password / empty and *
- Only root is allowed to set an empty password or one
- with only * in it.
- It will be refused (error message) for all other
- users.
- */
- if (getuid()==0){
- ret = 0;
- }else{
- xconf_error (
- MSG_U(E_NULLPASS,"Empty password not allowed.\n"
- "Only root is allowed to do so.\n"
- "This is not a good idea though!"));
- }
- }else{
- PASSWD_VALID vl;
- /* #Specification: userconf / password / checking
- userconf check the minimum length and the
- amount of non alpha character in a new password.
-
- If the new password does not fullfill the local
- policies, it is rejected.
- */
- if ((int)strlen(pass)>=vl.minlen){
- int nbalpha = 0;
- while (*pass != '\0'){
- if (!isalpha(*pass)) nbalpha++;
- pass++;
- }
- if (nbalpha >= vl.minnonalpha) ret = 0;
- }
- if (ret){
- xconf_error (MSG_U(E_WEAKPASS
- ,"Password not accepted\n"
- "Select a more complicated one\n"
- "The local policies are\n"
- "\n"
- "Minimum length : %d\n"
- "Minimum number of non-alpha character : %d\n")
- ,vl.minlen,vl.minnonalpha);
- }
- }
- return ret;
- }
-
- /*
- Update the passwd field with a new password and manage SHADOW
- */
- PRIVATE void USER::update_passwd (
- const char *newp,
- SHADOW *shadow,
- int is_lock)
- {
- SSTRING *pwd = &passwd;
- if (shadow != NULL){
- time_t tim = time(NULL);
- int days = tim/(24*60*60);
- if (is_lock){
- if (shadow->disable == 0){
- shadow->disable = days;
- shadow->last = days;
- }
- }else{
- pwd = &shadow->passwd;
- shadow->last = days;
- shadow->disable = 0;
- passwd.setfrom ("x");
- }
- }
- char buf[80];
- char *store = buf;
- strcpy (buf,newp);
- if (buf[0] != '\0' && strcmp(buf,"*")!=0){
- store = crypt(buf,buf);
- }
- pwd->setfrom (store);
- }
- /*
- Edit(set) the password of a user.
- Return -1 if the user escape without accepting the changes.
- If the user enter the password correctly and accept it, the
- object USER is updated with the crypted version.
- */
- PUBLIC int USER::editpass(
- int lock_available, // The dialog allow locking the account
- // Putting * in the password
- // or managing the SHADOW record properly
- SHADOW *shadow)
- {
- int ret = -1;
- while (1){
- DIALOG dia;
- char is_lock = passwd.cmp("*")==0;
- if (shadow) is_lock = shadow->disable != 0;
- if (lock_available){
- dia.newf_chk ("",is_lock,MSG_U(F_LOCKACCOUNT,"Lock the account"));
- }
- SSTRING buf1;
- dia.newf_pass (MSG_U(F_PASSWORD,"Password"),buf1);
- SSTRING buf2;
- dia.newf_pass (MSG_U(F_CONFIRM,"Confirmation"),buf2);
- char title[80];
- sprintf (title,MSG_U(T_PASSWORD,"%s's password"),name.get());
- if (dia.edit (title
- ,MSG_U(I_PASSINTRO
- ,"You must enter the new password twice\n"
- "To make sure you have enter it\n"
- "correctly.\n")
- ,help_password.getpath()
- ,0) != MENU_ACCEPT){
- break;
- }else if (lock_available && is_lock){
- if (buf1.is_empty() && buf2.is_empty()){
- update_passwd ("*",shadow,1);
- setmodified();
- ret = 0;
- break;
- }else{
- xconf_error (MSG_U(E_LOCKORNOT
- ,"A locked account does not have a password\n"
- "either you type a password(twice)\n"
- "either you lock the account and then\n"
- "you don't enter any passwords"));
-
- }
- }else if (buf1.cmp(buf2)!=0){
- xconf_error (MSG_U(E_MISMATCH
- ,"There was a mismatch\n"
- "Please try again\n"));
- }else if (!pass_isweak(buf1.get())){
- xconf_notice (MSG_U(N_ACCEPT,"New password for user %s accepted")
- ,name.get());
- update_passwd (buf1.get(),shadow,0);
- setmodified();
- ret = 0;
- break;
- }
- }
- return ret;
- }
- /*
- Edit the password of the current user.
- Ask for his current password to allow him to continue.
- */
- PUBLIC int USER::edithispass(SHADOW *shadow)
- {
- /* #Specification: userconf / passwd clone
- userconf can be a clone of the /bin/passwd program, If the
- proper symlink is done. When a user attempt to change
- his own password, the program prompt for the current one.
- */
- int ret = -1;
- char buf1[MAX_LEN+1];
- if (xconf_inputpass (
- MSG_U(T_CHGYOURPASS,"Changing your password")
- ,MSG_U(I_ENTERYOURPASS,"Please enter your current password\n")
- ,help_password
- ,buf1)==MENU_ACCEPT){
- if (strcmp(crypt(buf1,passwd.get()),passwd.get())==0){
- ret = editpass(0,shadow);
- }else{
- xconf_error (MSG_R(E_IVLDPASS));
- }
- }
- return ret;
- }
-
- /*
- non tty oriented passwd changing facility
- copy the functionnality of the old passwd program.
- */
- PUBLIC int USER::edithispass_notty(SHADOW *shadow)
- {
- int ret = -1;
- printf (MSG_U(W_CHGPASS,"Changing password for %s\n"),getname());
- printf (MSG_U(Q_ENTEROLDPASS,"Enter old password:"));
- fflush (stdout);
- char old[100];
- if (fgets (old,sizeof(old)-1,stdin) != NULL){
- printf (MSG_U(Q_ENTERNEWPASS,"Enter new password:"));
- fflush (stdout);
- char newp[100];
- if (fgets (newp,sizeof(newp)-1,stdin) != NULL){
- printf (MSG_U(Q_RETYPE,"Re-type new password:"));
- fflush (stdout);
- char newp2[100];
- if (fgets (newp2,sizeof(newp2)-1,stdin) != NULL){
- if (strcmp(crypt(old,passwd.get()),passwd.get())!=0){
- }else if (strcmp(newp,newp2)!=0){
- }else if (pass_isweak(newp)){
- }else{
- update_passwd (newp,shadow,0);
- ret = 0;
- }
- }
- }
- }
- return ret;
- }
-
- #ifdef TEST
-
- int main (int argc, char *argv[])
- {
- dialog_clear();
- USERS users;
- GROUPS groups;
- if (argc == 1){
- USER *user = new USER;
- if (user->edit(users,groups,1)==0){
- users.add (user);
- users.write();
- }else{
- delete user;
- }
- }else{
- USER *user = users.getitem(argv[1]);
- if (user != NULL && user.edit(users,groups,0)==0){
- users.write();
- }
- }
- return 0;
- }
-
- #endif
-
-
-
-